package org.wupgame.config.handle;

import com.alibaba.fastjson.JSON;
import jakarta.annotation.Resource;
import lombok.Data;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.wupgame.model.Player;
import org.wupgame.model.Room;
import org.wupgame.model.RoomState;
import org.wupgame.model.RoomStateMsg;
import org.wupgame.model.dto.ActionDTO;
import org.wupgame.model.e.Color;
import org.wupgame.repository.RoomRepository;
import org.wupgame.service.GameService;
import org.wupgame.service.e.GameException;
import org.wupgame.util.RedisUtil;

import java.io.IOException;
import java.net.URI;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.context.event.EventListener;
import org.wupgame.event.GameStateChangedEvent;

@Component
public class GameWebSocketHandler extends TextWebSocketHandler {
	private static final Logger logger = Logger.getLogger(GameWebSocketHandler.class.getName());
	// 存储会话
	private final Map<String, Map<Long, WebSocketSession>> roomSessions = new ConcurrentHashMap<>();
	@Resource
	private GameService gameService;
	@Resource
	private RoomRepository roomRepository;
	@Resource
	private RedisUtil redisUtil;
	
	/**
	 * 当WebSocket连接建立时调用此方法 主要处理玩家加入游戏房间的逻辑： 1. 从会话中提取房间ID和玩家ID 2. 将会话添加到房间会话映射中 3. 通知房间内其他玩家有新玩家加入 4. 向新玩家发送当前游戏状态
	 *
	 * @param session WebSocket会话对象，代表与客户端的连接
	 *
	 * @throws Exception 当处理过程中发生错误时抛出
	 */
	@Override
	public void afterConnectionEstablished(WebSocketSession session) throws Exception {
		// 从URL中获取房间ID和玩家ID
		String roomId = getRoomIdFromSession(session);
		Long playerId = getPlayerIdFromSession(session);
		
		// 添加会话到房间
		// 使用computeIfAbsent确保房间会话映射存在，如果不存在则创建新的ConcurrentHashMap
		roomSessions
			.computeIfAbsent(
				roomId,
				k -> new ConcurrentHashMap<>())
			.put(
				playerId,
				session);
		
		// 通知房间内其他玩家有新玩家加入
		// 通过广播消息告知房间内所有其他玩家，有新玩家加入了游戏
		broadcastToRoom(
			roomId,
			playerId,
			// 排除自己，不需要给自己发消息
			new Message(
				"playerJoined",
				// 消息类型：玩家加入
				playerId));      // 数据：加入的玩家ID
		
		// 向新加入的玩家发送游戏状态
		// 获取当前玩家的游戏状态并发送给该玩家，使其能够同步游戏界面
		RoomState roomState = getPlayerGames(playerId);
		if (roomState != null) {
			// 如果获取到游戏状态，则发送给玩家
			sendMessageToPlayer(
				session,
				new Message(
					"gameState",
					// 消息类型：游戏状态
					new RoomStateMsg(roomState, playerId)));   // 数据：房间状态对象
		}
	}
	
	/**
	 * 从WebSocket会话中获取玩家ID 支持从会话属性、URL路径或查询参数中提取玩家ID
	 *
	 * @param session WebSocket会话对象
	 *
	 * @return 玩家ID
	 * @throws IllegalArgumentException 当无法从会话中获取玩家ID时抛出
	 */
	private Long getPlayerIdFromSession(WebSocketSession session) {
		try {
			// 尝试从会话属性中获取
			Object playerIdObj = session
				.getAttributes()
				.get("playerId");
			if (playerIdObj != null) {
				if (playerIdObj instanceof Long) {
					return (Long) playerIdObj;
				} else if (playerIdObj instanceof String) {
					return Long.parseLong((String) playerIdObj);
				}
			}
			
			// 尝试从URL中解析
			URI uri = session.getUri();
			if (uri != null) {
				String path = uri.getPath();
				String query = uri.getQuery();
				
				// 尝试从路径中解析，格式如 /game/{roomId}/player/{playerId}
				if (path != null) {
					String[] pathParts = path.split("/");
					for (
						int i = 0;
						i < pathParts.length - 1;
						i++) {
						// 查找路径中"player"段的下一个部分作为玩家ID
						if ("player".equals(pathParts[i]) && i + 1 < pathParts.length) {
							try {
								return Long.parseLong(pathParts[i + 1]);
							}
							catch (NumberFormatException e) {
								// 忽略格式错误，继续尝试其他方式
							}
						}
					}
				}
				
				// 尝试从查询参数中解析，格式如 ?roomId={roomId}&playerId={playerId}
				if (query != null) {
					String[] queryParams = query.split("&");
					for (String param : queryParams) {
						String[] keyValue = param.split("=");
						// 查找名为"playerId"的查询参数
						if (keyValue.length == 2 && "playerId".equals(keyValue[0])) {
							try {
								return Long.parseLong(keyValue[1]);
							}
							catch (NumberFormatException e) {
								// 忽略格式错误，继续尝试其他方式
							}
						}
					}
				}
			}
			
			throw new IllegalArgumentException("无法从WebSocket会话中获取玩家ID");
		}
		catch (Exception e) {
			// 记录错误日志并重新抛出异常
			logger.log(
				Level.SEVERE,
				"获取玩家ID失败",
				e);
			throw e;
		}
	}
	
	/**
	 * 从WebSocket会话中获取房间ID 支持从会话属性、URL路径或查询参数中提取房间ID
	 *
	 * @param session WebSocket会话对象
	 *
	 * @return 房间ID
	 * @throws IllegalArgumentException 当无法从会话中获取房间ID时抛出
	 */
	private String getRoomIdFromSession(WebSocketSession session) {
		try {
			// 尝试从会话属性中获取
			Object roomIdObj = session
				.getAttributes()
				.get("roomId");
			if (roomIdObj != null) {
				return roomIdObj.toString();
			}
			
			// 尝试从URL中解析
			URI uri = session.getUri();
			if (uri != null) {
				String path = uri.getPath();
				String query = uri.getQuery();
				
				// 尝试从路径中解析，格式如 /game/{roomId}/player/{playerId}
				if (path != null) {
					String[] pathParts = path.split("/");
					for (
						int i = 0;
						i < pathParts.length - 1;
						i++) {
						// 查找路径中"room"段的下一个部分作为房间ID
						if ("room".equals(pathParts[i]) && i + 1 < pathParts.length) {
							return pathParts[i + 1];
						}
					}
				}
				
				// 尝试从查询参数中解析，格式如 ?roomId={roomId}&playerId={playerId}
				if (query != null) {
					String[] queryParams = query.split("&");
					for (String param : queryParams) {
						String[] keyValue = param.split("=");
						// 查找名为"roomId"的查询参数
						if (keyValue.length == 2 && "roomId".equals(keyValue[0])) {
							return keyValue[1];
						}
					}
				}
			}
			
			throw new IllegalArgumentException("无法从WebSocket会话中获取房间ID");
		}
		catch (Exception e) {
			// 记录错误日志并重新抛出异常
			logger.log(
				Level.SEVERE,
				"获取房间ID失败",
				e);
			throw e;
		}
	}
	
	/**
	 * 获取指定玩家的游戏状态 通过调用gameService的方法来获取玩家参与的所有游戏房间状态
	 *
	 * @param playerId 玩家ID
	 *
	 * @return 玩家的游戏状态，如果获取失败则返回null
	 */
	private RoomState getPlayerGames(Long playerId) {
		try {
			// 这里需要根据实际情况实现获取玩家游戏状态的逻辑
			// 可以创建一个简单的Player对象来调用gameService的方法
			return gameService.getPlayerGames((Player) createPlayer(playerId));
		}
		catch (Exception e) {
			// 记录警告日志，但不中断程序执行
			logger.log(
				Level.WARNING,
				"获取玩家游戏状态失败",
				e);
			return null;
		}
	}
	
	/**
	 * 通过反射创建Player对象并设置ID 当无法直接访问Player类的构造函数时，使用反射机制创建Player对象
	 *
	 * @param playerId 要设置的玩家ID
	 *
	 * @return 创建的Player对象
	 * @throws RuntimeException 当创建Player对象失败时抛出
	 */
	private Object createPlayer(Long playerId) {
		// 创建一个简单的Player对象或使用反射
		try {
			// 通过类名获取Player类对象
			Class<?> playerClass = Class.forName("org.wupgame.model.Player");
			// 使用无参构造函数创建Player实例
			Object player = playerClass
				.getDeclaredConstructor()
				.newInstance();
			// 获取Player类的id字段
			java.lang.reflect.Field idField = playerClass.getDeclaredField("id");
			// 设置字段可访问（即使它是私有的）
			idField.setAccessible(true);
			// 为创建的Player对象设置ID值
			idField.set(
				player,
				playerId);
			return player;
		}
		catch (Exception e) {
			// 记录严重错误日志并抛出运行时异常
			logger.log(
				Level.SEVERE,
				"创建Player对象失败",
				e);
			throw new RuntimeException(
				"创建Player对象失败",
				e);
		}
	}
	
	/**
	 * 处理WebSocket接收到的文本消息 根据消息类型执行相应的游戏操作（出牌、抽牌、质疑等）
	 *
	 * @param session WebSocket会话对象
	 * @param message 接收到的文本消息
	 *
	 * @throws Exception 当处理消息过程中发生错误时抛出
	 */
	@Override
	protected void handleTextMessage(
		WebSocketSession session,
		TextMessage message) throws Exception {
		try {
			// 解析消息内容为Message对象
			Message msg = JSON.parseObject(
				message.getPayload(),
				Message.class);
			
			// 从会话中获取房间ID和玩家ID
			String roomId = getRoomIdFromSession(session);
			Long playerId = getPlayerIdFromSession(session);
			
			// 处理不同类型的消息
			switch (msg.getType()) {
				case "playCard":
					// 处理出牌操作
					ActionDTO playCardAction = new ActionDTO();
					playCardAction.setRoomId(roomId);
					playCardAction.setPlayerId(playerId);
					playCardAction.setCardId(msg.getCardId());
					playCardAction.setChosenColor(msg.getChosenColor());
					playCardAction.setChallenge(false);
					gameService.processPlayerAction(playCardAction);
					// 广播游戏状态更新给所有玩家
					// broadcastGameState(roomId);
					break;
				
				case "drawCard":
					// 处理抽牌操作
					ActionDTO drawCardAction = new ActionDTO();
					drawCardAction.setRoomId(roomId);
					drawCardAction.setPlayerId(playerId);
					drawCardAction.setCardId(""); // 空字符串表示抽牌操作
					drawCardAction.setChallenge(false);
					gameService.processPlayerAction(drawCardAction);
					// 广播游戏状态更新给所有玩家
					// broadcastGameState(roomId);
					break;
				
				case "challenge":
					// 处理质疑操作（针对+4牌）
					ActionDTO challengeAction = new ActionDTO();
					challengeAction.setRoomId(roomId);
					challengeAction.setPlayerId(playerId);
					challengeAction.setCardId(""); // 质疑操作不需要指定卡牌ID
					challengeAction.setChallenge(true);
					gameService.processPlayerAction(challengeAction);
					// 广播游戏状态更新给所有玩家
					// broadcastGameState(roomId);
					break;
				
				default:
					// 处理未知类型的消息
					logger.warning("收到未知类型的消息: " + msg.getType());
					sendMessageToPlayer(
						session,
						new Message(
							"error",
							"未知的消息类型"));
			}
		}
		catch (GameException e) {
			// 处理游戏异常，向玩家发送错误信息
			logger.warning("游戏操作异常: " + e.getMessage());
			sendMessageToPlayer(
				session,
				new Message(
					"actionError",
					e.getMessage()));
		}
		catch (Exception e) {
			// 处理其他未预期的异常
			logger.log(
				Level.SEVERE,
				"处理消息时发生错误",
				e);
			sendMessageToPlayer(
				session,
				new Message(
					"error",
					"处理请求时发生错误"));
		}
	}
	
	
	/**
	 * 处理WebSocket连接关闭事件 当玩家断开连接时，从房间会话中移除该玩家，并通知其他玩家有玩家离开
	 *
	 * @param session 关闭的WebSocket会话对象
	 * @param status  连接关闭状态信息
	 *
	 * @throws Exception 当处理连接关闭过程中发生错误时抛出
	 */
	@Override
	public void afterConnectionClosed(
		WebSocketSession session,
		CloseStatus status) throws Exception {
		try {
			// 从会话中获取房间ID和玩家ID
			String roomId = getRoomIdFromSession(session);
			Long playerId = getPlayerIdFromSession(session);
			
			// 从房间会话映射中移除该玩家的会话
			Map<Long, WebSocketSession> sessions = roomSessions.get(roomId);
			if (sessions != null) {
				// 移除玩家会话
				sessions.remove(playerId);
				// 如果房间中没有其他玩家了，则完全移除该房间
				if (sessions.isEmpty()) {
					roomSessions.remove(roomId);
				}
			}
			
			// 通知房间内其他玩家有玩家离开
			broadcastToRoom(
				roomId,
				null,
				// 不排除任何玩家
				new Message(
					"playerLeft",
					playerId));
			
			// 调用游戏服务处理玩家离开游戏的逻辑
			try {
				gameService.leaveGame(
					roomId,
					(org.wupgame.model.Player) createPlayer(playerId));
			}
			catch (Exception e) {
				// 记录警告日志，但不中断连接关闭流程
				logger.log(
					Level.WARNING,
					"处理玩家离开游戏失败",
					e);
			}
		}
		catch (Exception e) {
			// 记录严重错误日志
			logger.log(
				Level.SEVERE,
				"关闭连接时发生错误",
				e);
		}
	}
	
	
	/**
	 * 处理WebSocket传输错误 当WebSocket连接发生传输错误时调用，记录错误日志并调用父类的错误处理方法
	 *
	 * @param session   发生错误的WebSocket会话对象
	 * @param exception 抛出的异常对象
	 *
	 * @throws Exception 当处理错误过程中发生其他错误时抛出
	 */
	@Override
	public void handleTransportError(
		WebSocketSession session,
		Throwable exception) throws Exception {
		// 记录传输错误的详细日志
		logger.log(
			Level.SEVERE,
			"WebSocket传输错误",
			exception);
		// 调用父类方法处理错误，确保标准的错误处理流程得到执行
		super.handleTransportError(
			session,
			exception);
	}
	
	
	// 广播游戏状态到房间内所有玩家
	
	
	/**
	 * 广播游戏状态到房间内所有玩家 获取最新的房间状态并发送给房间内的所有玩家
	 *
	 * @param roomId 房间ID
	 */
	// 广播游戏状态到房间内所有玩家
	public void broadcastGameState(String roomId) {
		try {
			// 从仓库中获取房间信息
			Room room = roomRepository
				.findById(roomId)
				.orElse(null);
			if (room == null) return;

			// 将Room对象转换为RoomState对象（假设存在转换方法）
			Map<Long, WebSocketSession> sessions = roomSessions.get(roomId);
			if (sessions == null) return;

			RoomState roomState = convertToRoomState(room);
			sessions.forEach((playerId, session) -> {
				String jsonMessage = JSON.toJSONString(new Message("gameState", new RoomStateMsg(roomState, playerId)));
				try {
					// 向玩家发送消息
					session.sendMessage(new TextMessage(jsonMessage));
				}
				catch (IOException e) {
					// 记录向特定玩家发送消息失败的日志
					logger.log(
						Level.WARNING,
						"向玩家 " + playerId + " 发送消息失败",
						e);
				}
			});
		}
		catch (Exception e) {
			// 记录广播游戏状态失败的错误日志
			logger.log(
				Level.SEVERE,
				"广播游戏状态失败",
				e);
		}
	}
	
	
	/**
	 * 将Room对象转换为RoomState对象 从Redis中获取房间的最新状态信息
	 *
	 * @param room Room对象
	 *
	 * @return 转换后的RoomState对象，如果转换失败则返回null
	 */
	private RoomState convertToRoomState(Room room) {
		// 根据实际情况实现Room到RoomState的转换逻辑
		// 这里仅作为示例
		try {
			// 从Redis中获取房间ID对应的RoomState对象
			return (RoomState) redisUtil.get(room.getId());
		}
		catch (Exception e) {
			// 记录转换失败的错误日志
			logger.log(
				Level.SEVERE,
				"转换Room到RoomState失败",
				e);
			return null;
		}
	}
	
	
	/**
	 * 广播消息到房间内所有玩家（除了excludePlayerId） 向指定房间内的所有玩家发送消息，可以选择排除某个特定玩家
	 *
	 * @param roomId          房间ID
	 * @param excludePlayerId 需要排除的玩家ID（不会收到消息），如果为null则不排除任何玩家
	 * @param message         要发送的消息对象
	 */
	// 广播消息到房间内所有玩家（除了excludePlayerId）
	private void broadcastToRoom(
		String roomId,
		Long excludePlayerId,
		Message message) {
		// 获取房间内所有的WebSocket会话
		Map<Long, WebSocketSession> sessions = roomSessions.get(roomId);
		if (sessions == null) {
			// 如果房间不存在会话则直接返回
			return;
		}
		
		// 将消息对象序列化为JSON字符串
		String jsonMessage = JSON.toJSONString(message);
		
		// 遍历房间内所有玩家的会话
		sessions.forEach((playerId, session) -> {
			// 检查条件：排除的玩家ID为null或者当前玩家不是被排除的玩家，并且会话处于开启状态
			if ((excludePlayerId == null || ! playerId.equals(excludePlayerId)) && session.isOpen()) {
				try {
					// 向玩家发送消息
					session.sendMessage(new TextMessage(jsonMessage));
				}
				catch (IOException e) {
					// 记录向特定玩家发送消息失败的日志
					logger.log(
						Level.WARNING,
						"向玩家 " + playerId + " 发送消息失败",
						e);
				}
			}
		});
	}
	
	
	/**
	 * 向特定玩家发送消息 通过WebSocket会话向指定玩家发送消息
	 *
	 * @param session WebSocket会话对象
	 * @param message 要发送的消息对象
	 */
	// 向特定玩家发送消息
	private void sendMessageToPlayer(
		WebSocketSession session,
		Message message) {
		// 检查会话是否存在且处于开启状态
		if (session != null && session.isOpen()) {
			try {
				// 将消息对象序列化为JSON字符串
				String jsonMessage = JSON.toJSONString(message);
				// 通过WebSocket会话发送消息
				session.sendMessage(new TextMessage(jsonMessage));
			}
			catch (IOException e) {
				// 记录发送消息失败的日志
				logger.log(
					Level.WARNING,
					"发送消息失败",
					e);
			}
		}
	}
	
	
	/**
	 * 游戏消息内部类 用于封装WebSocket通信中传递的消息数据
	 */
	@Data
	public static class Message {
		/**
		 * 消息类型 例如："gameState"（游戏状态）、"playCard"（出牌）、"drawCard"（抽牌）等
		 */
		private String type;
		
		/**
		 * 消息数据内容 根据消息类型不同，包含不同的数据对象
		 */
		private Object data;
		
		/**
		 * 卡牌ID 当消息涉及具体卡牌操作时使用
		 */
		private String cardId;
		
		/**
		 * 选择的颜色 当玩家需要选择颜色时使用（例如玩+4牌或变色牌时）
		 */
		private Color chosenColor;
		
		/**
		 * 构造函数，用于创建包含类型和数据的消息对象
		 *
		 * @param type 消息类型
		 * @param data 消息数据
		 */
		public Message(
			String type,
			Object data) {
			this.type = type;
			this.data = data;
		}
	}
	


	// 监听服务层发布的房间状态变更事件，并广播给房间内所有玩家
	@EventListener
	public void onGameStateChanged(GameStateChangedEvent event) {
		try {
			broadcastGameState(event.getRoomId());
		}
		catch (Exception e) {
			logger.log(Level.SEVERE, "处理房间状态变更事件失败", e);
		}
	}
}